from pathlib import Path # 导入路径工具以读取本地行业分类文件
import numpy as np # 导入数值工具以检查有限值与守恒
import pandas as pd # 导入表格工具以组织持仓原始字段
answer_file=Path('/home/ubuntu/r2_data_mount/data/stock/stock_basic_data.h5') # 固定行业映射文件
answer_raw=pd.DataFrame([('000001.OF','20250930','20251028','603986.SH',2.69),('000001.OF','20250930','20251028','601689.SH',2.72),('000001.OF','20250930','20251028','300308.SZ',3.74),('000001.OF','20250930','20251028','002475.SZ',2.72),('000001.OF','20250930','20251028','002371.SZ',2.97),('000001.OF','20250930','20251028','688347.SH',3.80),('000001.OF','20250930','20251028','688041.SH',2.58),('000001.OF','20250930','20251028','002025.SZ',5.38),('000001.OF','20250930','20251028','688019.SH',2.49),('000001.OF','20250930','20251028','600276.SH',3.00),('000011.OF','20250930','20251028','300750.SZ',8.43),('000011.OF','20250930','20251028','300408.SZ',2.83),('000011.OF','20250930','20251028','002475.SZ',4.54),('000011.OF','20250930','20251028','601899.SH',3.26),('000011.OF','20250930','20251028','002371.SZ',2.86),('000011.OF','20250930','20251028','688052.SH',3.11),('000011.OF','20250930','20251028','688617.SH',2.69),('000011.OF','20250930','20251028','688088.SH',3.45),('000011.OF','20250930','20251028','600522.SH',3.05),('000011.OF','20250930','20251028','600276.SH',2.79)],columns=['ts_code','end_date','ann_date','symbol','stk_mkv_ratio']) # 按官方字段名重新计算两基金持仓摘录
if not answer_file.exists() or not {'ts_code','end_date','ann_date','symbol','stk_mkv_ratio'}.issubset(answer_raw.columns): raise RuntimeError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 检查文件来源与字段
answer_raw['order_book_id']=answer_raw['symbol'].str.replace('.SH','.XSHG',regex=False).str.replace('.SZ','.XSHE',regex=False) # 规范化官方symbol证券代码
answer_basic=pd.read_hdf(answer_file,key='stock_basic_info') # 读取本地行业分类快照
if not {'order_book_id','citics_2019_l1_name'}.issubset(answer_basic.columns): raise RuntimeError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 检查行业分类字段
answer_map=answer_basic[['order_book_id','citics_2019_l1_name']].drop_duplicates('order_book_id') # 形成唯一证券行业映射
answer_mapped=answer_raw.merge(answer_map,on='order_book_id',how='left',validate='many_to_one') # 对两基金执行同一行业映射
if answer_mapped['citics_2019_l1_name'].isna().any() or not np.isfinite(answer_mapped['stk_mkv_ratio']).all(): raise RuntimeError('输入文件、字段、样本量或数值不符合当前分析要求,请按本页说明检查') # 执行资产覆盖与数值要求
answer_sum=answer_mapped.groupby(['ts_code','citics_2019_l1_name'])['stk_mkv_ratio'].sum() # 聚合两基金前十大持仓占股票持仓市值比
answer_remaining_stock=100-answer_sum.groupby(level=0).sum() # 在同一股票市值分母内计算其余股票持仓
answer_sum=pd.concat([answer_sum,pd.Series(answer_remaining_stock.values,index=pd.MultiIndex.from_arrays([answer_remaining_stock.index,['其余股票持仓']*len(answer_remaining_stock)]))]) # 补齐两基金股票持仓市值整体
answer_stock_share=answer_sum.unstack(0).fillna(0.0) # 形成逐基金占股票持仓市值比矩阵
assert np.allclose(answer_stock_share.sum(axis=0),100.0) and (answer_stock_share>=0).all().all() # 检查两基金在股票持仓市值分母内守恒与非负
print({'来源':'Tushare fund_portfolio字段摘录;访问日2026-08-26','官方字段':'stk_mkv_ratio','分母':'占股票持仓市值比','持仓报告期':'2025-09-30','行业文件':str(answer_file),'key':'stock_basic_info','分类字段':'citics_2019_l1_name','分类边界':'当前静态快照映射历史持仓;未验证报告期历史分类','证券覆盖率':answer_mapped['citics_2019_l1_name'].notna().mean(),'同分母比例和':answer_stock_share.sum().to_dict()}) # 输出可核对本章计算与分母边界